home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
EnigmA Amiga Run 1996 June
/
EnigmA AMIGA RUN 08 (1996)(G.R. Edizioni)(IT)[!][issue 1996-06][EARSAN CD VII].iso
/
earcd
/
cmdity
/
clicker.lha
/
Clicker
/
Source
/
sound.c
< prev
next >
Wrap
C/C++ Source or Header
|
1996-05-17
|
8KB
|
255 lines
/* File: sound.c
* Created: 20-10-95
* Updated: 17-05-96
* Version: 1.3
* Project: Clicker
* Owner: Jeroen Vermeulen
* Requirements: KickStart V39+
* Legal: PD
* Status: Release
*/
#include <math.h>
#include <proto/exec.h>
#include <exec/devices.h>
#include <exec/memory.h>
#include <devices/audio.h>
#include <proto/alib.h>
#include "sample.h"
#include "main.h"
#include "sound.h"
#include "prefs.h"
static STRPTR
AllocFailPubMem = "Not enough PUBLIC memory!\n",
OpenFailAudioDev = "Can't open audio.device\n";
/* Allocation priority array for sound channels. I don't much care which one
* we get; we only need one.
*/
static unsigned char SoundChannels[] = { 1,2,4,8 };
/* CreateSample():
* Open audio device and sample. An IOAudio structure is returned unless either
* an error occurs or the error string was already non-NULL before the call.
* ClickPrefs must also be properly initialized before KeyClick() can be called.
*/
struct IOAudio *CreateSample(STRPTR *const error)
{
struct IOAudio *soundrequest = NULL;
struct IORequest *plainrequest;
struct MsgPort *Reply;
if ((Reply = CreateMsgPort()))
{
plainrequest = CreateIORequest(Reply,sizeof(struct IOAudio));
soundrequest = (struct IOAudio *)plainrequest;
if (soundrequest)
{
if (OpenDevice("audio.device",0,plainrequest,0) == 0)
{
/* We've succeeded in opening the audio.device, which is all that
* matters right now. We'll try to allocate a sound channel, but
* KeyClick() will know what to do if we fail.
*/
plainrequest->io_Message.mn_Node.ln_Pri = -128; /* Never steal channels */
plainrequest->io_Command = ADCMD_ALLOCATE;
plainrequest->io_Flags = ADIOF_NOWAIT;
soundrequest->ioa_Data = SoundChannels;
soundrequest->ioa_Length = sizeof(SoundChannels);
BeginIO(plainrequest);
WaitIO(plainrequest);
/* Now set up for first click! To think I forgot this once and have
* been after the bug for weeks...
* The lesson is: Never assume you're not stupid enough for something.
* (I know what you're thinking: "I may be stupid but I'm not stupid
* enough to make _that_ assumption")
*/
plainrequest->io_Command = CMD_WRITE;
plainrequest->io_Flags = ADIOF_PERVOL;
soundrequest->ioa_Data = (signed char *)Sample;
soundrequest->ioa_Length = SAMPLELENGTH;
}
else *error = OpenFailAudioDev;
}
else *error = AllocFailPubMem;
}
else *error = AllocFailMsgPort;
if (*error)
{
DeleteSample(soundrequest);
soundrequest = NULL;
}
return soundrequest;
}
/* Destroy sample and free all resources allocated with it. This function is
* overly robust so it can be used from within CreateSample() in case of an
* error.
*/
void DeleteSample(struct IOAudio *const soundrequest)
{
if (soundrequest)
{
struct IORequest *const plainrequest = &soundrequest->ioa_Request;
struct MsgPort *const ReplyPort = plainrequest->io_Message.mn_ReplyPort;
const struct Device *const io_Device = plainrequest->io_Device;
/* --- */
if (io_Device && ((long)io_Device != -1)) CloseDevice(plainrequest);
DeleteIORequest(plainrequest);
if (ReplyPort) DeleteMsgPort(ReplyPort);
}
}
/* KeyClick():
* Make key-click noise. The soundrequest pointer is assumed to be valid and
* non-NULL, and point at a properly initialized IOAudio structure.
* The IOAudio structure must be set up for a WRITE request; this is its
* default state to which it is also restored at the end of the function.
* This function accesses ClickPrefs; make sure it is set up properly (and set
* its "newsettings" flag for the initial call).
*
* If the previous click failed due to the channel being stolen, we attempt to
* allocate a new channel.
*/
void KeyClick(struct IOAudio *const soundrequest)
{
struct IORequest *const plainrequest = &soundrequest->ioa_Request;
/* --- */
/* Allocate channel if necessary */
if (plainrequest->io_Error) /* Channel stolen or allocation failed */
{
plainrequest->io_Error = 0;
plainrequest->io_Flags = ADIOF_NOWAIT;
plainrequest->io_Command = ADCMD_ALLOCATE;
soundrequest->ioa_Data = SoundChannels;
soundrequest->ioa_Length = sizeof(SoundChannels);
soundrequest->ioa_AllocKey = 0;
BeginIO(plainrequest);
/* This line is in a strange place, admittedly. I put it here just so I
* might catch a little low-level parallellism on the off-chance.
* If I have to do my audio I/O asynchronously I expect to be able to
* exploit some of that somewhere. This is my chance!
*/
ClickPrefs.newsettings = TRUE; /* Reload sound settings before click */
WaitIO(plainrequest);
if (plainrequest->io_Error) return;
/* Set up for click */
plainrequest->io_Command = CMD_WRITE;
soundrequest->ioa_Data = (signed char *)Sample;
soundrequest->ioa_Length = SAMPLELENGTH;
}
/* Prefs */
if (ClickPrefs.newsettings)
{
ClickPrefs.newsettings = FALSE;
soundrequest->ioa_Period = ClickPrefs.period;
soundrequest->ioa_Volume = ClickPrefs.volume;
soundrequest->ioa_Cycles = ClickPrefs.cycles;
plainrequest->io_Flags = ADIOF_PERVOL;
}
/* Click */
BeginIO(plainrequest);
WaitIO(plainrequest);
/* Sound request is ready for a new write command on exit */
}
/* SliderToHertz():
* Converts a prefs window slider position (between -5*12 and 4*12) to a
* frequency in Hertz, based on a twelve-tone octave centered at the 440 Hz A.
*/
LONG SliderToHertz(const struct Gadget *const dum, const WORD sliderpos)
{
/* Frequency is 440 * 2^(n/12). Pity we can't use left-shift here!
*/
return (LONG)(440.0 * pow(2.0,sliderpos/12.0));
}
/* HertzToPeriod():
* Converts human-readable pitch in Hertz to period length suitable for use by
* audio.device. As a rule, HertzToPeriod(SliderToHertz(S)) is equivalent to
* SliderToPeriod(S).
*/
UWORD HertzToPeriod(const LONG Hertz)
{
/* Period should be computed from frequency f as 10^9 / (279.365 * s * f),
* where s is the sample length. With proper constant folding, this should
* compile to (C/Hertz) where C is a constant.
*/
return (UWORD)((1000000000.0/(279.365*SAMPLELENGTH))/Hertz);
}
/* PeriodToHertz():
* Converts audio.device period length to human-readable pitch in Hertz. This
* is the inverse of HertzToPeriod().
*/
LONG PeriodToHertz(const UWORD period)
{
/* Pitch in Hertz is computed from period f as 279.365 * s * p / 10^9, where s
* is sample length.
*/
return (LONG)((279.365 * SAMPLELENGTH / 1000000000.0) * period);
}
/* SliderToPeriod():
* Converts a prefs window slider position (between 0 and 9*12) to a period
* length (in units of 279.365 nanoseconds) suitable for use by audio.device.
*/
UWORD SliderToPeriod(const WORD sliderpos)
{
/* For a frequency f, the period length is 10^9 / (279.365 * s * f). Here s
* is the length of the waveform sample (SAMPLELENGTH).
* So we need to compute 10^9 / (279.365 * s * (440 * 2^(n/12)))
* == 2^(-n/12) * 10^9 / (279.365 * 440 * s)
* == 2^(-n/12) * 10^9 / (122920.6 * s)
*/
return (UWORD)(pow(2.0,-(double)sliderpos/12.0) *
(10000000.0 / (1229.206 * SAMPLELENGTH)));
}
/* PeriodToSlider():
* Inverse of SliderToPeriod(). Takes a period length as an argument and
* computes the appropriate slider position (between -5*12 and 4*12). This
* function can afford to be slow because it's only ever called when the prefs
* window pops up.
*/
WORD PeriodToSlider(const UWORD period)
{
/* Since the period number p can be computed from slider position n by
* p = 10^9 / (279.365 * 440 * s * 2^(n/12)) <==>
* 2^(n/12) = 10^9 / (279.365 * 440 * s * p) <==>
* n/12 = 2log(10^9 / (279.365 * 440 * s * p)) <==>
* n = 12 * 2log(10^9 / (279.365 * 440 * s * p)) ==
* 12 * 2log(10^9 / (122920.6 * s * p)) ==
* 12 * ln(10^9 / (122920.6 * s * p)) / ln(2)
*/
return (WORD)(12.0 * log(
1000000000.0 / (122920.6*SAMPLELENGTH*period)
)/log(2.0));
}